// 1、用于解析 {{ xxx }} 插值表达式和 v-model、v-text、v-on 等指令
// 2、重要：其中在第一次编译表达式和指令时，触发observe的getter，然后将callback传递过去；使用dep.addSub(new Watcher())将callback放到update()里面；后续更新
export default class Compile {
    constructor(vm){
        let el = vm.$el
        let node = document.querySelector(el) // 根据选择器查询 node
        this.vm = vm
        this.data = vm.$data
        this.transferNode(node)

    }

    transferNode(el) {
        // childNodes 能获取所有的节点类型（包括纯文本），children 只能获取 dom 标签节点
        let childNodes = el && Array.from(el.childNodes)
        if(childNodes && childNodes.length){
            childNodes.forEach(node => {
                this.transferNode(node) // 递归处理所有子节点

                if(this.isNodeElement(node)){ // 是 v-model 这样的指令节点，而且是标签节点
                    this.compileElement(node)
                }

                if(this.isNodeText(node)){ // 是纯文本节点
                    this.compileText(node)
                }
            })
        }
    }
    // 1. 渲染节点，解析指令，例如：v-model、v-text、v-html
    compileElement(node) {
        let attrs = node.attributes
        if(attrs && attrs.length){
            for(let attr of attrs){
                if(this.isDrective(attr.name)){
                    this.update(node, attr)
                }
            }
        }
    }
    // 更新指令 
    update(node, attr) {
        // v-on 指令特殊处理
        if(attr.name.startsWith('v-on')){
            let eventName = attr.name.substr(5) // 事件名称
            let eventCaller = attr.nodeValue // 事件调用方法
            this.onUpdater(node, eventName, eventCaller)
            return
        }

        // 其它指令：v-model、v-text、v-html
        let key = attr.nodeValue //例如：v-mode对应的变量名：msg
        let name = attr.name.substr(2) //例如：v-model的处理函数名：model
        let updater = this[`${name}Updater`]
        updater && updater.call(this, node, key)
    }

    // v-text
    textUpdater(node, key){
        this.renderNode(node, 'textContent', key)
    }
    // v-model
    modelUpdater(node, key){
        this.renderNode(node, 'value', key)

        // 双向绑定，将dom元素里面输入的值同步到data
        node.addEventListener('input',(e)=>{
            this.data[key] = e.target.value
        })
    }
    // v-html
    htmlUpdater(node, key){
        this.renderNode(node, 'innerHTML', key)
    }
    
    // v-on
    onUpdater(node, eventName, eventCaller){
        node.addEventListener(eventName,()=>{
            eval(eventCaller)
        })
    }

    // 公用的更新dom数据方法，根据属性来更新：
    // text 的 attr 为：textContent
    // input 的 attr 为：value
    renderNode(node, attr, key) {
        // 用于更新数据时更新视图
        // 绑定一个callback到vm，用于watcher的updater()调用
        this.vm.$callback[key] = (newValue) => {
            node[attr] = newValue
        }

        // 用于首次加载数据时的赋值
        // 在赋值时，在getter中将callback放到new Watcher()里面
        // 为什么要赋值为null？因为只能添加一次订阅；
        // 虽然赋值为null，但是callback在Watcher中已经保留了下来；
        node[attr] = this.data[key]
        this.vm.$callback[key] = null
    }

    // 2. 渲染文本
    compileText(node) {
        // 匹配插值表达式格式：{{ xxx }}
        let reg = /\{\{(.+?)\}\}/
        let content = node.textContent
        if(reg.test(content)){
            let key = node.textContent.replaceAll(/\{|\}/g,'').trim()
            this.renderNode(node, 'textContent', key)
        }
    }

    isDrective(name){
        return name.startsWith('v-')
    }
    isNodeElement(node) {
        return node.nodeType === 1
    }
    isNodeText(node) {
        return node.nodeType === 3
    }
}